namespace TfsExtensions.Build.Activities
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Text.RegularExpressions;
    using System.Xml;
    using TfsExtensions.Build;
    using TfsExtensions.Build.Activities.Entities;
    using System.Activities;
    using Microsoft.TeamFoundation.Build.Client;

    public class UpdateSolution : CodeActivity
    {
        private const string BeginProjectSectionTag = "ProjectSection(WebsiteProperties) = preProject";
        private const string BeginProjectTag = "Project(\"{E24C65DC-7377-472B-9ABA-BC803B73C61A}\")";
        private const string DEBUG_MODE = "Debug";
        private const string EndProjectSectionTag = "EndProjectSection";
        private const string EndProjectTag = "EndProject";
        private const string ExtractProjectsFromSolutionRegex = @"=\s*""(?<ProjectName>.+?)""\s*,\s*""(?<ProjectFile>.+?)""\s*,\s*""(?<ProjectGUID>.+?)""";
        private const string RELEASE_MODE = "Release";
        private const string WEBSITE_DELAYSIGN = ".AspNetCompiler.DelaySign";
        private const string WEBSITE_KEYCONTAINER = ".AspNetCompiler.KeyContainer";
        private const string WEBSITE_KEYFILE = ".AspNetCompiler.KeyFile";

        public InArgument<IEnumerable<String>> SolutionFilesToProcess
        {
            get;
            set;
        }

        public InArgument<AssemblySettings> AssemblyMetadataSettings { get; set; }

        protected override void Execute(CodeActivityContext context)
        {
            var result = this.ExecuteInternal(context);
        }

        private bool ExecuteInternal(CodeActivityContext context)
        {
            try
            {
                bool result = true;
                foreach (string solutionFile in this.SolutionFilesToProcess.Get(context))
                {
                    LogHelper.LogMessage(context, BuildMessageImportance.Normal, "Located solution file {0}", new object[] { solutionFile });

                    string projectPathRelativeToSolution = String.Empty;
                    string projectPathOnDisk = String.Empty;
                    string solutionFolder = String.Empty;
                    string solutionText = String.Empty;
                    bool isWebsitePresent = false;

                    if (!File.Exists(solutionFile))
                    {
                        LogHelper.LogError(context,String.Format("Not able to locate solution file {0} on disk.",solutionFile));
                        return false;
                    }

                    solutionFolder = Path.GetDirectoryName(solutionFile);
                    this.ChangeReadOnlyStatus(solutionFile);
                    solutionText = File.ReadAllText(solutionFile);
                    MatchCollection matches = Regex.Matches(solutionText, ExtractProjectsFromSolutionRegex);

                    for (int i = 0; i < matches.Count; i++)
                    {
                        try
                        {
                            projectPathRelativeToSolution = matches[i].Groups["ProjectFile"].Value;
                            projectPathOnDisk = Path.Combine(solutionFolder, projectPathRelativeToSolution);

                            if (!File.Exists(projectPathOnDisk))
                            {
                                isWebsitePresent = true;
                                break;
                            }
                        }
                        catch (BuildTasksException ex)
                        {
                            LogHelper.LogError(context, "Project: " + projectPathOnDisk + "; Exception: " + ex.Message);
                            result = false;
                        }
                    }

                    if (isWebsitePresent)
                    {
                        LogHelper.LogError(context, String.Format("Updating solution file {0}", solutionFile));
                        this.UpdateSolutionFile(solutionFile,this.AssemblyMetadataSettings.Get(context));
                    }
                }

                return result;
            }
            catch (Exception ex)
                {
                LogHelper.LogError(context, "Error processing request: " + ex.Message);
                return false;
            }
        }

        /// <summary>
        /// Changes the read only status.
        /// </summary>
        /// <param name="filePath">The file path.</param>
        private void ChangeReadOnlyStatus(string filePath)
        {
            if ((File.GetAttributes(filePath) & FileAttributes.ReadOnly) == FileAttributes.ReadOnly)
            {
                FileAttributes attribs = File.GetAttributes(filePath);
                attribs &= ~FileAttributes.ReadOnly;
                File.SetAttributes(filePath, attribs);
            }
        }

        /// <summary>
        /// Updates the solution file.
        /// </summary>
        /// <param name="fileName">Name of the file.</param>
        private void UpdateSolutionFile(string fileName, AssemblySettings settings )
        {
            string line;
            bool isWebSiteProject = false;
            bool isProjectSection = false;
            string folderName = Path.GetDirectoryName(fileName);
            string tempFile = folderName + @"/" + Guid.NewGuid() + ".txt";
            using (var sr = new StreamReader(fileName))
            {
                using (var sw = new StreamWriter(tempFile))
                {
                    var propList = new List<string>();
                    while ((line = sr.ReadLine()) != null)
                    {
                        if (line.StartsWith(BeginProjectTag, StringComparison.Ordinal))
                        {
                            isWebSiteProject = true;
                            sw.WriteLine(line);
                        }
                        else if (line.StartsWith(EndProjectTag, StringComparison.Ordinal) && isWebSiteProject)
                        {
                            isWebSiteProject = false;
                            sw.WriteLine(line);
                        }
                        else if (line.IndexOf(BeginProjectSectionTag, StringComparison.Ordinal) >= 0 && isWebSiteProject)
                        {
                            isProjectSection = true;
                            sw.WriteLine(line);
                            propList = new List<string>();
                        }
                        else if (line.IndexOf(EndProjectSectionTag, StringComparison.Ordinal) >= 0 && isWebSiteProject)
                        {
                            isProjectSection = false;
                            if (settings.AssemblySigningDetails.IsAssemblySigning)
                            {
                                if (settings.AssemblySigningDetails.IsDelaySigned)
                                {
                                    propList.Add("\t\t" + DEBUG_MODE + WEBSITE_DELAYSIGN + " = \"true\"");
                                    propList.Add("\t\t" + RELEASE_MODE + WEBSITE_DELAYSIGN + " = \"true\"");
                                }

                                if (!string.IsNullOrEmpty(settings.AssemblySigningDetails.StrongKeyFilePath))
                                {
                                    var signKeyContent = settings.AssemblySigningDetails.SignKeyContent;
                                    var signKeyFilePath = settings.AssemblySigningDetails.StrongKeyFilePath;
                                    if (signKeyContent != null && signKeyContent.Length > 0)
                                    {
                                        var keyfile = signKeyFilePath;
                                        keyfile = keyfile.Substring(keyfile.LastIndexOf(@"\") + 1);
                                        string filePath = Path.Combine(Path.GetTempPath(), keyfile);

                                        using (var mstream = new MemoryStream(signKeyContent))
                                        {
                                            using (var fstream = new FileStream(filePath, FileMode.Create, FileAccess.Write))
                                            {
                                                mstream.WriteTo(fstream);
                                                fstream.Flush();
                                                mstream.Flush();
                                            }
                                        }

                                        propList.Add("\t\t" + DEBUG_MODE + WEBSITE_KEYFILE + " = \"" + filePath + "\"");
                                        propList.Add("\t\t" + RELEASE_MODE + WEBSITE_KEYFILE + " = \"" + filePath + "\"");
                                    }
                                }

                                if (!string.IsNullOrEmpty(settings.AssemblySigningDetails.StrongKeyName))
                                {
                                    propList.Add("\t\t" + DEBUG_MODE + WEBSITE_KEYCONTAINER + " = \"" + settings.AssemblySigningDetails.StrongKeyName + "\"");
                                    propList.Add("\t\t" + RELEASE_MODE + WEBSITE_KEYCONTAINER + " = \"" + settings.AssemblySigningDetails.StrongKeyName + "\"");
                                }
                            }

                            foreach (string element in propList)
                            {
                                sw.WriteLine(element);
                            }

                            sw.WriteLine(line);
                            propList = null;
                        }
                        else
                        {
                            if (isWebSiteProject && isProjectSection)
                            {
                                bool isPresent = false;
                                if (line.IndexOf(RELEASE_MODE + "." + WEBSITE_DELAYSIGN, StringComparison.Ordinal) >= 0)
                                {
                                    isPresent = true;
                                }

                                if (line.IndexOf(RELEASE_MODE + "." + WEBSITE_KEYFILE, StringComparison.Ordinal) >= 0)
                                {
                                    isPresent = true;
                                }

                                if (line.IndexOf(RELEASE_MODE + "." + WEBSITE_KEYCONTAINER, StringComparison.Ordinal) >= 0)
                                {
                                    isPresent = true;
                                }

                                if (line.IndexOf(DEBUG_MODE + "." + WEBSITE_DELAYSIGN, StringComparison.Ordinal) >= 0)
                                {
                                    isPresent = true;
                                }

                                if (line.IndexOf(DEBUG_MODE + "." + WEBSITE_KEYFILE, StringComparison.Ordinal) >= 0)
                                {
                                    isPresent = true;
                                }

                                if (line.IndexOf(DEBUG_MODE + "." + WEBSITE_KEYCONTAINER, StringComparison.Ordinal) >= 0)
                                {
                                    isPresent = true;
                                }

                                if (!isPresent)
                                {
                                    propList.Add(line);
                                }
                            }
                            else
                            {
                                sw.WriteLine(line);
                            }
                        }
                    }
                }
            }
            File.Delete(fileName);
            File.Move(tempFile, fileName);
        }
    }
}